package edu.kit.aifb.ruleintegrator.repository;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;

import joptsimple.OptionParser;
import joptsimple.OptionSet;

import org.apache.log4j.Logger;
import org.semanticweb.yars.nx.Node;
import org.semanticweb.yars.nx.Resource;
import org.semanticweb.yars.nx.Variable;
import org.semanticweb.yars.nx.namespace.RDF;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;

import edu.kit.aifb.ats.ATSBaseRepository;
import edu.kit.aifb.ats.Configuration;
import edu.kit.aifb.ats.triples.TripleRepository;
import edu.kit.aifb.ats.triples.data.Table;
import edu.kit.aifb.ats.triples.model.BGPQuery;
import edu.kit.aifb.ats.triples.model.TriplePattern;
import edu.kit.aifb.ats.triples.model.Var;
import edu.kit.aifb.ats.triples.query.Evaluator;
import edu.kit.aifb.ruleintegrator.util.Util;


public class RepositoryStatistics {
	private static final Logger log = Logger.getLogger(RepositoryStatistics.class);

	private TripleRepository m_tr;
	
	
	List<Resource> m_relations;
	
	List<Resource> m_attributes;
	
	List<Resource> m_classes;
	Map<Resource,RelationStats> m_relationStats;
	Map<Resource,ClassStats> m_clsStats; 
	
	public RepositoryStatistics(TripleRepository tr) throws IOException{
		this.m_tr=tr;
		this.m_relations=getRelations();
		this.m_attributes=getAttributes();
		this.m_classes=getClasses();
		this.m_relationStats=new HashMap<Resource, RelationStats>();
		this.m_clsStats=new HashMap<Resource, ClassStats>();
	}
	
	public RepositoryStatistics(List<Resource> rels,List<Resource> atts, List<Resource> clss,Map<Resource,RelationStats> relstats,Map<Resource,ClassStats> clsStats){
		this.m_relations=rels;
		this.m_attributes=atts;
		this.m_classes=clss;
		this.m_clsStats=clsStats;
		this.m_relationStats=relstats;
	}
	
	public void collect() throws IOException{
	
		for(Resource rel:m_relations){
			List<Resource> srcCls=getSourceClasses(rel);
			List<Resource> tgtCls=getTargetClasses(rel);			
			List<Resource> srcRels=getSourceRelations(rel);			
			List<Resource> tgtRels=getTargetRelations(rel);
			List<Resource> srcAtts=getSourceAttributes(rel);
			List<Resource> tgtAtts=getTargetAttributes(rel);
			RelationStats relStats=new RelationStats(rel,srcCls,tgtCls,srcRels,tgtRels,srcAtts,tgtAtts);
			this.m_relationStats.put(rel, relStats);			
		}
		
		for(Resource cls:m_classes){
			List<Resource> rels=getClassRelations(cls);
			List<Resource> atts=getClassAttributes(cls);
			ClassStats clsStats=new ClassStats(cls,rels,atts);
			this.m_clsStats.put(cls, clsStats);
			
		}
		

	}
	
	private List<Resource> getRelations() throws IOException{
		List<Resource> result=new ArrayList<Resource>();
		SortedSet<Resource> preds=m_tr.getPredicates();
		for(Resource pr:preds){
			if(m_tr.isObjectProperty(pr) && !(pr.toURI().toString().equals(RDF.TYPE.toString()))){
				result.add(pr);
			}
		}
		
		return result;		
	}

	private List<Resource> getAttributes() throws IOException{
		List<Resource> result=new ArrayList<Resource>();
		SortedSet<Resource> preds=m_tr.getPredicates();
		for(Resource pr:preds){
			if(m_tr.isDatatypeProperty(pr)){
				result.add(pr);
			}
		}
		
		return result;		
	}
	
	private List<Resource> getClasses() throws IOException{
		List<Resource> result=new ArrayList<Resource>();

		Variable entVar=Var.name("ent");
		Variable clsVar=Var.name("cls");
		
		// we need to use real variable names here, Var.name is a shortcut for this (instead of 'new Variable("s")')
		TriplePattern qp1 = new TriplePattern(entVar, RDF.TYPE, clsVar);
		
		
		// FIXME select variables are ignored for now, leave list empty here
		BGPQuery query = new BGPQuery(new ArrayList<Variable>(), Arrays.asList(qp1));

		Evaluator eval = m_tr.createBGPQueryEvaluator();
		
		// execute
		Table<Node> res = eval.evaluate(query);
		for(Node[] row:res.getRows()){
			Node cn=res.getValue(row, clsVar.toN3());
			if(cn instanceof Resource){
				Resource cls=(Resource)cn;
				if(!result.contains(cls))
					result.add(cls);
			}
		}
		return result;
	}
	
	
	private List<Resource> getSourceClasses(Resource relation) throws IOException{
		List<Resource> result=new ArrayList<Resource>();
		Variable ent1Var=Var.name("ent1");
		Variable ent2Var=Var.name("ent2");
		Variable clsVar=Var.name("cls");
		
		// we need to use real variable names here, Var.name is a shortcut for this (instead of 'new Variable("s")')
		TriplePattern qp1 = new TriplePattern(ent1Var, RDF.TYPE, clsVar);
		TriplePattern qp2 = new TriplePattern(ent1Var, relation, ent2Var);
		BGPQuery query = new BGPQuery(new ArrayList<Variable>(), Arrays.asList(qp1,qp2));

		Evaluator eval = m_tr.createBGPQueryEvaluator();
		
		// execute
		Table<Node> res = eval.evaluate(query);
		for(Node[] row:res.getRows()){
			Node cn=res.getValue(row, clsVar.toN3());
			if(cn instanceof Resource){
				Resource cls=(Resource)cn;
				if(!result.contains(cls))
					result.add(cls);
			}
		}
		
		
		return result;
		
	}
	
	private List<Resource> getSourceRelations(Resource relation) throws IOException{
		List<Resource> result=new ArrayList<Resource>();

		
		Variable ent1Var=Var.name("ent1");
		
		Variable relVar=Var.name("rel");
		
		// we need to use real variable names here, Var.name is a shortcut for this (instead of 'new Variable("s")')
		TriplePattern qp1 = new TriplePattern(ent1Var, relVar, null);
		TriplePattern qp2 = new TriplePattern(ent1Var, relation, null);
		BGPQuery query = new BGPQuery(new ArrayList<Variable>(), Arrays.asList(qp1,qp2));

		Evaluator eval = m_tr.createBGPQueryEvaluator();
		// execute
		Table<Node> res = eval.evaluate(query);
		for(Node[] row:res.getRows()){
			Node cn=res.getValue(row, relVar.toN3());
			if(cn instanceof Resource){
				Resource rel=(Resource)cn;
				if(this.m_relations.contains(rel) && !result.contains(rel))
					result.add(rel);
			}
		}

		return result;
	}
	
	private List<Resource> getTargetRelations(Resource relation) throws IOException{
		List<Resource> result=new ArrayList<Resource>();

		
		Variable ent1Var=Var.name("ent1");
		
		Variable relVar=Var.name("rel");
		
		// we need to use real variable names here, Var.name is a shortcut for this (instead of 'new Variable("s")')
		TriplePattern qp1 = new TriplePattern(ent1Var, relVar, null);
		TriplePattern qp2 = new TriplePattern(null, relation, ent1Var);
		BGPQuery query = new BGPQuery(new ArrayList<Variable>(), Arrays.asList(qp1,qp2));

		Evaluator eval = m_tr.createBGPQueryEvaluator();
		// execute
		Table<Node> res = eval.evaluate(query);
		for(Node[] row:res.getRows()){
			Node cn=res.getValue(row, relVar.toN3());
			if(cn instanceof Resource){
				Resource rel=(Resource)cn;
				if(this.m_relations.contains(rel) && !result.contains(rel))
					result.add(rel);
			}
		}

		return result;
	}
	
	private List<Resource> getTargetClasses(Resource relation) throws IOException{
		List<Resource> result=new ArrayList<Resource>();
		Variable ent1Var=Var.name("ent1");
		Variable ent2Var=Var.name("ent2");
		Variable clsVar=Var.name("cls");
		
		// we need to use real variable names here, Var.name is a shortcut for this (instead of 'new Variable("s")')
		TriplePattern qp1 = new TriplePattern(ent1Var, RDF.TYPE, clsVar);
		TriplePattern qp2 = new TriplePattern(ent2Var, relation, ent1Var);
		BGPQuery query = new BGPQuery(new ArrayList<Variable>(), Arrays.asList(qp1,qp2));

		Evaluator eval = m_tr.createBGPQueryEvaluator();
		
		// execute
		Table<Node> res = eval.evaluate(query);
		for(Node[] row:res.getRows()){
			Node cn=res.getValue(row, clsVar.toN3());
			if(cn instanceof Resource){
				Resource cls=(Resource)cn;
				if(this.m_classes.contains(cls) && !result.contains(cls))
					result.add(cls);
			}
		}
		return result;		
	}
	
	private List<Resource> getSourceAttributes(Resource relation) throws IOException{
		List<Resource> result=new ArrayList<Resource>();

		
		Variable ent1Var=Var.name("ent1");
		
		Variable attVar=Var.name("att");
		
		// we need to use real variable names here, Var.name is a shortcut for this (instead of 'new Variable("s")')
		TriplePattern qp1 = new TriplePattern(ent1Var, attVar, null);
		TriplePattern qp2 = new TriplePattern(ent1Var, relation, null);
		BGPQuery query = new BGPQuery(new ArrayList<Variable>(), Arrays.asList(qp1,qp2));

		Evaluator eval = m_tr.createBGPQueryEvaluator();
		// execute
		Table<Node> res = eval.evaluate(query);
		for(Node[] row:res.getRows()){
			Node cn=res.getValue(row, attVar.toN3());
			if(cn instanceof Resource){
				Resource att=(Resource)cn;
				if(this.m_attributes.contains(att) && !result.contains(att))
					result.add(att);
			}
		}

		return result;
	}
	
	private List<Resource> getTargetAttributes(Resource relation) throws IOException{
		List<Resource> result=new ArrayList<Resource>();

		
		Variable ent1Var=Var.name("ent1");
		
		Variable attVar=Var.name("att");
		
		// we need to use real variable names here, Var.name is a shortcut for this (instead of 'new Variable("s")')
		TriplePattern qp1 = new TriplePattern(ent1Var, attVar, null);
		TriplePattern qp2 = new TriplePattern(null, relation, ent1Var);
		BGPQuery query = new BGPQuery(new ArrayList<Variable>(), Arrays.asList(qp1,qp2));

		Evaluator eval = m_tr.createBGPQueryEvaluator();
		// execute
		Table<Node> res = eval.evaluate(query);
		for(Node[] row:res.getRows()){
			Node cn=res.getValue(row, attVar.toN3());
			if(cn instanceof Resource){
				Resource att=(Resource)cn;
				if(this.m_attributes.contains(att) && !result.contains(att))
					result.add(att);
			}
		}

		return result;
	}
	
	private List<Resource> getClassRelations(Resource cls) throws IOException{
		List<Resource> result=new ArrayList<Resource>();

		
		Variable ent1Var=Var.name("ent1");
		
		Variable relVar=Var.name("relVar");
		
		// we need to use real variable names here, Var.name is a shortcut for this (instead of 'new Variable("s")')
		TriplePattern qp1 = new TriplePattern(ent1Var, relVar, null);
		TriplePattern qp2 = new TriplePattern(ent1Var, RDF.TYPE, cls);
		BGPQuery query = new BGPQuery(new ArrayList<Variable>(), Arrays.asList(qp1,qp2));

		Evaluator eval = m_tr.createBGPQueryEvaluator();
		// execute
		Table<Node> res = eval.evaluate(query);
		for(Node[] row:res.getRows()){
			Node cn=res.getValue(row, relVar.toN3());
			if(cn instanceof Resource){
				Resource rel=(Resource)cn;
				if(this.m_relations.contains(rel) && !result.contains(rel))
					result.add(rel);
			}
		}

		return result;
	}
	
	
	private List<Resource> getClassAttributes(Resource cls) throws IOException{
		List<Resource> result=new ArrayList<Resource>();

		
		Variable ent1Var=Var.name("ent1");
		
		Variable attVar=Var.name("attVar");
		
		// we need to use real variable names here, Var.name is a shortcut for this (instead of 'new Variable("s")')
		TriplePattern qp1 = new TriplePattern(ent1Var, attVar, null);
		TriplePattern qp2 = new TriplePattern(ent1Var, RDF.TYPE, cls);
		BGPQuery query = new BGPQuery(new ArrayList<Variable>(), Arrays.asList(qp1,qp2));

		Evaluator eval = m_tr.createBGPQueryEvaluator();
		// execute
		Table<Node> res = eval.evaluate(query);
		for(Node[] row:res.getRows()){
			Node cn=res.getValue(row, attVar.toN3());
			if(cn instanceof Resource){
				Resource att=(Resource)cn;
				if(this.m_attributes.contains(att) && !result.contains(att))
					result.add(att);
			}
		}

		return result;
	}
	
	public List<Resource> getRelationList(){
		return this.m_relations;
	}
	
	public List<Resource> getClassList(){
		return this.m_classes;
	}
	
	public List<Resource> getAttributeList(){
		return m_attributes;
	}
	
	public void setAttributeList(List<Resource> attributes){
		this.m_attributes=attributes;
	}
	
	public RelationStats getRelationStats(Resource rel){
		return this.m_relationStats.get(rel);
	}
	
	public ClassStats getClassStats(Resource cls){
		return this.m_clsStats.get(cls);
	}
	
	public void write(String wd) throws IOException{
		if(!wd.endsWith("/"))
			wd=wd+"/";
		String path=wd+"stats/stats.yml";

		File file=Util.ensureFile(path);
//		DumperOptions options = new DumperOptions();
//	    options.setCanonical(true);
//	    Constructor constructor = new Constructor(RepositoryStatistics.class);//Car.class is root
//	    TypeDescription rsDescription = new TypeDescription(RepositoryStatistics.class);
//	    rsDescription.putListPropertyType("attributeList", Resource.class);
//	    constructor.addTypeDescription(rsDescription);
	    
	    DumperOptions options = new DumperOptions();
	    options.setDefaultFlowStyle(DumperOptions.FlowStyle.AUTO);

		Yaml yaml=new Yaml(new RSRepresenter(),options);
		yaml.dump(this, new FileWriter(file));		
		
		
		
		
	}

	public static RepositoryStatistics load(String wd) throws IOException{
		if(!wd.endsWith("/"))
			wd=wd+"/";
		String path=wd+"stats/stats.yml";


//		DumperOptions options = new DumperOptions();
//	    options.setCanonical(true);
//	    Constructor constructor = new Constructor(RepositoryStatistics.class);//Car.class is root
//	    TypeDescription rsDescription = new TypeDescription(RepositoryStatistics.class);
//	    rsDescription.putListPropertyType("attributeList", Resource.class);
//	    constructor.addTypeDescription(rsDescription);
	    
	    DumperOptions options = new DumperOptions();
	    options.setDefaultFlowStyle(DumperOptions.FlowStyle.AUTO);

		Yaml yaml=new Yaml(new RSConstructor());
		return (RepositoryStatistics)yaml.load(new FileReader(path));
		
	}

	
	
	
	/**
	 * @param args
	 * @throws IOException 
	 */
	public static void main(String[] args) throws IOException {
		OptionParser op = new OptionParser();
		op.accepts("d", "output directory")
			.withRequiredArg().ofType(String.class).describedAs("directory");
		op.accepts("w", "working folder")
		.withRequiredArg().ofType(String.class).describedAs("Working Folder");
//		op.accepts("db", "db file")
//		.withRequiredArg().ofType(String.class);
		OptionSet os = op.parse(args);

		if (!os.has("d") || (!os.has("w"))) {
			op.printHelpOn(System.out);
			return;
		}
		
		String directory = (String)os.valueOf("d");
		String workingdirectory = (String)os.valueOf("w");
		
		ATSBaseRepository baseRepo = new ATSBaseRepository(new File(directory));
		TripleRepository tr=null; 
		
		tr=baseRepo.getSubRepository(TripleRepository.class,false, 
					new Configuration().set(TripleRepository.PARAM_TYPE, TripleRepository.TYPE_TRIPLES));
		
		
//		StructuralEM em=new StructuralEM(null, tr);
//		em.initModel();
		RepositoryStatistics stats=new RepositoryStatistics(tr);
		stats.collect();
		stats.write(workingdirectory);
		
	
		
		tr.close();
		
		
	
		
		
		
		

	}

	
}
